package com.gitee.wsl.android.viewmodels

import android.content.Context
import android.net.Uri
import android.widget.Toast
import androidx.lifecycle.ViewModel
import androidx.room.RoomDatabase
import androidx.sqlite.db.SimpleSQLiteQuery
import com.gitee.wsl.android.R
import com.gitee.wsl.ext.K
import com.gitee.wsl.ext.tryOrNull
import com.gitee.wsl.jvm.ext.store.div
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import timber.log.Timber
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
import kotlin.system.exitProcess

abstract class BackupRestoreViewModel<D: RoomDatabase> (
    val database: D,
    val settingFileName:String,
    val dbFileName:String
) : ViewModel() {
    fun backup(context: Context, uri: Uri) {
        runCatching {
            context.applicationContext.contentResolver.openOutputStream(uri)?.use {
                it.buffered().zipOutputStream { outputStream ->
                    (context.filesDir / "datastore" / settingFileName).inputStream().buffered().use { inputStream ->
                        //outputStream.addEntry(SETTINGS_FILENAME,inputStream)
                        outputStream.putNextEntry(ZipEntry(dbFileName))
                        inputStream.copyTo(outputStream)
                    }
                    runBlocking(Dispatchers.IO) {
                        database.checkpoint()
                    }
                    FileInputStream(database.openHelper.writableDatabase.path).use { inputStream ->
                        outputStream.putNextEntry(ZipEntry(dbFileName))
                        inputStream.copyTo(outputStream)
                    }
                }
            }
        }.onSuccess {
            Toast.makeText(context, R.string.backup_create_success, Toast.LENGTH_SHORT).show()
        }.onFailure {
            reportException(it)
            Toast.makeText(context, R.string.backup_create_failed, Toast.LENGTH_SHORT).show()
        }
    }

    fun restore(context: Context, uri: Uri) {
        runCatching {
            context.applicationContext.contentResolver.openInputStream(uri)?.use {
                it.zipInputStream{ inputStream ->
                    var entry = tryOrNull { inputStream.nextEntry } // prevent ZipException
                    while (entry != null) {
                        when (entry.name) {
                            settingFileName -> {
                                (context.filesDir / "datastore" / settingFileName).outputStream().use { outputStream ->
                                    inputStream.copyTo(outputStream)
                                }
                            }

                            dbFileName -> {
                                runBlocking(Dispatchers.IO) {
                                    database.checkpoint()
                                }
                                database.close()
                                FileOutputStream(database.openHelper.writableDatabase.path).use { outputStream ->
                                    inputStream.copyTo(outputStream)
                                }
                            }
                        }
                        entry = tryOrNull { inputStream.nextEntry } // prevent ZipException
                    }
                }
            }
            restoreSuccess()
            exitProcess(0)
        }.onFailure {
            reportException(it)
            Toast.makeText(context, R.string.restore_failed, Toast.LENGTH_SHORT).show()
        }
    }

    fun reportException(it: Throwable){
        Timber.d(it)
    }

    /**
     * context.stopService(Intent(context, MusicService::class.java))
     *             context.filesDir.resolve(PERSISTENT_QUEUE_FILE).delete()
     *             context.startActivity(Intent(context, MainActivity::class.java))
     */
    abstract fun restoreSuccess()


    companion object {
        const val SETTINGS_FILENAME = "settings.preferences_pb"
    }
}

private fun InputStream.zipInputStream(): ZipInputStream = ZipInputStream(this)

private fun OutputStream.zipOutputStream(): ZipOutputStream = ZipOutputStream(this)

private fun InputStream.zipInputStream(block:(ZipInputStream)->Unit) = ZipInputStream(this).use(block)

private fun OutputStream.zipOutputStream(block:(ZipOutputStream)->Unit) = ZipOutputStream(this).use(block)

fun RoomDatabase.checkpoint() {
    query(SimpleSQLiteQuery("PRAGMA wal_checkpoint(FULL)"))
}